package com.own.component.util.time.model;

import com.own.component.util.time.LocalTimeUtil;
import com.own.component.util.time.TimetableUtil;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * TimetableModel
 * <p>
 * 1.需要将时间升序排序（时间不能前后重叠）
 * 2.每次加入要判断的时间，需要重新进行数据校验（如果校验通过，说明能够将该时间排进去）
 *
 * @author chenxueli
 * @date 2021/3/10 19:41
 */
public class TimetableModel {

    /**
     * N个维度的时间列表（初始化列表）
     */
    private final List<Set<TimeExtent>> INIT_LIST = new ArrayList<>();

    /**
     * 需要排除时间的列表
     */
    private final List<TimeExtent> extentList = new ArrayList<>();

    /**
     * 配置属性
     */
    private final TimetableConfig config;

    /**
     * 空参构造
     */
    public TimetableModel() {
        // 手动初始化配置参数
        this(new TimetableConfig());
    }

    /**
     * 配置参数构造
     *
     * @param config 配置信息
     */
    public TimetableModel(TimetableConfig config) {
        this.config = config;
    }

    /**
     * 空参构造
     *
     * @param range 初始化时间范围
     */
    public TimetableModel(String range) {
        this(new TimetableConfig(), range);
    }

    /**
     * 配置参数构造
     *
     * @param config 配置信息
     * @param range  初始化时间范围
     */
    public TimetableModel(TimetableConfig config, String range) {
        this(config);
        format(range);
    }

    public static void main(String[] args) {
        // 创建配置信息
        var config = new TimetableConfig(3);
        var formatter = DateTimeFormatter.ofPattern("HH:mm");
        config.setResolveFormatter(formatter);
        config.setExportFormatter(formatter);
        // 创建时间范围
        var content = "09:00-21:00";
        // 构建时间表数据
        var timetable = new TimetableModel(config, content);
        // 添加排除时间
        timetable.exclude(LocalTime.of(9, 0, 0), LocalTime.of(11, 0, 0));
        timetable.exclude(LocalTime.of(9, 0, 0), LocalTime.of(11, 0, 0));
        timetable.exclude(LocalTime.of(9, 0, 0), LocalTime.of(10, 58, 0));
        timetable.exclude(LocalTime.of(10, 59, 0), LocalTime.of(15, 0, 0));
        timetable.exclude(LocalTime.of(10, 58, 0), LocalTime.of(10, 59, 0));
        timetable.exclude(LocalTime.of(13, 58, 0), LocalTime.of(13, 59, 0));
        timetable.exclude(LocalTime.of(16, 0, 0), LocalTime.of(17, 0, 0));
        timetable.exclude(LocalTime.of(16, 0, 0), LocalTime.of(17, 0, 0));
        timetable.exclude(LocalTime.of(16, 0, 0), LocalTime.of(17, 0, 0));
        // 执行验证
        var start = System.currentTimeMillis();
        // 输出数据
        var string = timetable.buildTimetable();
        System.out.println(System.currentTimeMillis() - start + "：" + string);
    }

    /**
     * 格式化
     *
     * @param range 时间范围
     */
    public void format(String range) {
        format(range, config);
    }

    /**
     * 格式化
     *
     * @param range  时间范围
     * @param config 配置信息
     */
    public void format(String range, TimetableConfig config) {
        var array = range.split(config.getItemSplitContent());
        var resultList = new ArrayList<Set<TimeExtent>>();
        for (int i = 0; i < config.getDefaultVacant(); i++) {
            resultList.add(new HashSet<>());
        }
        Arrays.stream(array).forEach(item -> {
            // 循环维度
            var extent = new TimeExtent();
            var tempArray = item.split(config.getTimeSplitContent());
            extent.setStart(LocalTime.parse(tempArray[0], config.getResolveFormatter()));
            extent.setEnd(LocalTime.parse(tempArray[1], config.getResolveFormatter()));
            for (int i = 0; i < config.getDefaultVacant(); i++) {
                resultList.get(i).add(extent.copy());
            }
        });
        INIT_LIST.addAll(resultList);
    }

    /**
     * 添加排除时间
     *
     * @param start 开始时间
     * @param end   结束时间
     */
    public void exclude(LocalTime start, LocalTime end) {
        if (start.isBefore(end)) {
            extentList.add(new TimeExtent(start, end));
        }
    }

    /**
     * 添加排除时间
     *
     * @param range 排除的时间范围
     */
    public void exclude(String range) {
        var array = range.split(config.getItemSplitContent());
        Arrays.stream(array).forEach(item -> {
            var tempArray = item.split(config.getTimeSplitContent());
            exclude(LocalTime.parse(tempArray[0], config.getResolveFormatter()), LocalTime.parse(tempArray[1], config.getResolveFormatter()));
        });
    }

    /**
     * 验证时间节点
     *
     * @return 是否可插入
     */
    public boolean check() {
        return check(null, null);
    }

    /**
     * 验证时间范围
     *
     * @param range 时间范围
     * @return 是否可以插入
     */
    public boolean check(String range) {
        var array = range.split(config.getItemSplitContent());
        var extentList = Arrays.stream(array).map(item -> {
            var tempArray = item.split(config.getTimeSplitContent());
            return new TimeExtent(LocalTime.parse(tempArray[0], config.getResolveFormatter()), LocalTime.parse(tempArray[1], config.getResolveFormatter()));
        }).collect(Collectors.toList());
        return check(extentList);
    }

    /**
     * 验证时间节点
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return 是否可插入
     */
    public boolean check(LocalTime startTime, LocalTime endTime) {
        var tempAddList = new ArrayList<TimeExtent>();
        if (startTime != null && endTime != null) {
            tempAddList.add(new TimeExtent(startTime, endTime));
        }
        return check(tempAddList);
    }

    /**
     * 验证时间节点
     *
     * @param waitExtentList 待验证的时间列表
     * @return 是否可插入
     */
    public boolean check(List<TimeExtent> waitExtentList) {
        List<TimeExtent> extentList = this.extentList;
        if (waitExtentList != null && waitExtentList.size() > 0) {
            extentList = Stream.concat(this.extentList.stream(), waitExtentList.stream()).collect(Collectors.toList());
        }
        // 从主初始化的列表中复制一份列表
        var list = copyList();
        // 将数据排序
        var resultList = extentList.stream()
                .sorted(Comparator.comparing(TimeExtent::getStart))
                .map(extent -> TimetableUtil.checkAvailableRange(extent, list));
        return resultList.allMatch(item -> item);
    }

    /**
     * 生成时间表
     *
     * @return 时间表
     */
    public String buildTimetable() {
        // 从主初始化的列表中复制一份列表
        var list = copyList();
        // 将数据排序
        var flagList = extentList.stream()
                .sorted(Comparator.comparing(TimeExtent::getStart))
                .map(extent -> TimetableUtil.checkAvailableRange(extent, list));
        var check = flagList.allMatch(item -> item);
        if (!check) {
            return "";
        }
        var resultList = handleAdvance(TimetableUtil.merge(list));
        return resultList.stream()
                .sorted(Comparator.comparing(TimeExtent::getStart))
                .map(item -> item.buildTimetable(config))
                .distinct()
                .filter(Objects::nonNull)
                .collect(Collectors.joining(config.getItemSplitContent()));
    }

    /**
     * 处理提前量
     *
     * @param list 列表
     * @return 新列表
     */
    public Collection<TimeExtent> handleAdvance(Collection<TimeExtent> list) {
        if (!config.isAdvance()) {
            return list;
        }
        var now = LocalTime.now().toSecondOfDay()
                + (config.getMinute() % 60) * 60
                + (config.getHour() % 24) * 60 * 60
                + config.getSecond() % 60;
        var time = now > 24 * 60 * 60 ? LocalTime.of(23, 59, 59) : (LocalTime.now()
                .plusMinutes(config.getMinute() % 60)
                .plusHours(config.getHour() % 24)
                .plusSeconds(config.getSecond() % 60));
        return list.stream()
                .filter(Objects::nonNull)
                .map(extent -> {
                    // 如果这个时间全在后面
                    if (extent.getEnd().isBefore(time)) {
                        return null;
                    }
                    if (extent.getStart().isBefore(time)) {
                        extent.setStart(LocalTime.of(time.getHour(), 0, 0));
                    }
                    return extent;
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * 复制列表
     *
     * @return 复制后的列表
     */
    private List<Set<TimeExtent>> copyList() {
        var list = new ArrayList<Set<TimeExtent>>();
        INIT_LIST.forEach(values -> {
            var set = new HashSet<TimeExtent>();
            values.forEach(extent -> {
                set.add(extent.copy());
            });
            list.add(set);
        });
        return list;
    }

    /**
     * 设置是否设置提前量
     *
     * @param flag 参数
     */
    public void advance(boolean flag) {
        this.config.setAdvance(flag);
    }

    @EqualsAndHashCode
    @NoArgsConstructor
    public static class TimeExtent {
        /**
         * 开始时间
         */
        @Getter
        private LocalTime start;
        /**
         * 结束时间
         */
        @Getter
        private LocalTime end;

        public TimeExtent(LocalTime start, LocalTime end) {
            this.start = start;
            this.end = end;
        }

        public void setStart(LocalTime start) {
            this.start = start;
        }

        public void setEnd(LocalTime end) {
            this.end = end;
        }

        /**
         * 是否可用
         *
         * @return 是否可用
         */
        public boolean available() {
            return end.isAfter(start);
        }

        /**
         * 生成时间表
         *
         * @param config 配置信息
         * @return 时间表
         */
        public String buildTimetable(TimetableConfig config) {
            if (!available()) {
                return null;
            }
            return start.format(config.getExportFormatter()) + config.getTimeSplitContent() + end.format(config.getExportFormatter());
        }

        /**
         * 生成时间表
         *
         * @param formatter 格式化格式
         * @param split     分隔符
         * @return 时间表
         */
        public String buildTimetable(DateTimeFormatter formatter, String split) {
            if (!available()) {
                return null;
            }
            return start.format(formatter) + split + end.format(formatter);
        }

        /**
         * 复制
         *
         * @return 复制后的对象
         */
        public TimeExtent copy() {
            return new TimeExtent(LocalTimeUtil.copy(this.start), LocalTimeUtil.copy(this.end));
        }

    }

}
